#include "socket_wrapper.h"

#include <assert.h>
#include <core/QbLogger.h>
#include <string.h>
#include <time.h>

#include "socket_inc.h"

#if defined(__ANDROID__)
#define SO_NOSIGPIPE MSG_NOSIGNAL
#endif

namespace qb {
#ifdef _MSC_VER
class SockedInit {
 public:
  SockedInit() {
    WSAData wsaData;
    if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
      log_error("socket init failed");
    }
  }
  ~SockedInit() {
    if (::WSACleanup() != 0) {
      log_warning("socket uninit failed(GetSockError=%d)",
                  CSocketWrapper::GetSockError());
    }
  }
};
static SockedInit g_sock_init_inst;
#endif
int32_t CSocketWrapper::GetSockError() {
#ifdef _MSC_VER
  return WSAGetLastError();
#else
  return errno;
#endif
}
CSocketWrapper::CSocketWrapper()
    : m_closed(true),
      m_uTargetIP(0),
      m_uTargetPort(0),
      m_socket(INVALID_SOCKET) {
  m_addr_info = new sockaddr_in;
}

CSocketWrapper::~CSocketWrapper() {
  delete static_cast<sockaddr_in*>(m_addr_info);
}

int32_t CSocketWrapper::Create(uint16_t socket_type) {
  if (m_socket != INVALID_SOCKET) {
    Close();
  }
  if (socket_type == IPPROTO_TCP) {
    m_socket = (uint32_t)::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  } else {
    m_socket = (uint32_t)::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  }
  if (m_socket == SOCKET_ERROR) {
    m_socket = INVALID_SOCKET;
    log_error("socket failed(% d)", GetSockError());
    return -1;
  }

  m_closed = false;
  return 0;
}

int32_t CSocketWrapper::Close() {
  if (!m_closed) {
    m_closed = true;
  }
  if (m_socket != INVALID_SOCKET) {
#ifdef _MSC_VER
    closesocket(m_socket);
#else
    close(m_socket);
#endif
    m_socket = INVALID_SOCKET;
  }

  return 0;
}

int32_t CSocketWrapper::SetOpt() {
  int32_t ret = 0;
  static int32_t nSendSize = 128 * 1024;
  static int32_t nRecvSize = 128 * 1024;
  if (SOCKET_ERROR == ::setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF,
                                   (char*)&nSendSize, sizeof(nSendSize))) {
    log_warning("setsockopt(SO_SNDBUF) failed(%d)", GetSockError());
    ret = -1;
  }
  if (SOCKET_ERROR == ::setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF,
                                   (char*)&nRecvSize, sizeof(nRecvSize))) {
    log_warning("setsockopt(SO_RCVBUF) failed(%d)", GetSockError());
    ret = -1;
  }

  return ret;
}

int32_t CSocketWrapper::SetTcpOpt() {
  int32_t ret = 0;
#ifdef _MSC_VER
  static int32_t nFlag = 1;

  if (SOCKET_ERROR == ::setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY,
                                   (char*)&nFlag, sizeof(nFlag))) {
    log_warning("setsockopt(TCP_NODELAY) failed(%d)", GetSockError());
    ret = -1;
  }
#else
  int set = 1;
  //::setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int));

#ifdef __linux__

  setsockopt(m_socket, SOL_SOCKET, MSG_NOSIGNAL, &set, sizeof(int));

#else

  setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(int));

#endif

#endif

  return ret;
}

int32_t CSocketWrapper::SetKeepalive() {
#ifdef _MSC_VER
  const char chOpt = 1;  // True
  // Set KeepAlive 开启保活机制, 防止服务端产生死连接
  if (setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, (char*)&chOpt,
                 sizeof(chOpt)) == 0) {
    DWORD cbBytesReturned = chOpt;
    tcp_keepalive klive;
    klive.onoff = 1;                      // 启用保活
    klive.keepalivetime = 1000 * 60 * 1;  // 1分钟超时 Keepalive
    klive.keepaliveinterval = 1000 * 5;  // 重试间隔为5秒 Resend if No-Reply
    WSAIoctl(m_socket, SIO_KEEPALIVE_VALS, &klive, sizeof(tcp_keepalive), NULL,
             0, &cbBytesReturned, 0, NULL);
    return 0;
  }
#endif

  return 0;
}

int32_t CSocketWrapper::BindRandPort(uint16_t& local_port) {
  int32_t ret = -1;
  srand((uint32_t)time(NULL));

  sockaddr_in localAddr;
  memset(&localAddr, 0, sizeof(localAddr));
  localAddr.sin_family = AF_INET;
  localAddr.sin_addr.s_addr = htonl(INADDR_ANY);

  for (int32_t i = 0; i < 10; i++) {
    uint16_t port = (60000 - 2048 + 1) * rand() + 2048;
    localAddr.sin_port = htons(port);
    if (bind(m_socket, (sockaddr*)&localAddr, sizeof(localAddr)) == 0) {
      local_port = port;
      ret = 0;
      break;
    }
  }

  return ret;
}

int32_t CSocketWrapper::GetFirstIPAddress(std::string& local_ipaddress) {
  char szHostName[128] = {0};
  if (gethostname(szHostName, 128) == 0) {
    hostent* ent = gethostbyname(szHostName);
    if (ent) {
      local_ipaddress = inet_ntoa(*(in_addr*)ent->h_addr_list[0]);
      return 0;
    }
  }
  log_error("dns gethostbyname(%s) falied, getlasterror(%d)",
            local_ipaddress.c_str(), GetSockError());
  return -1;
}

int32_t CSocketWrapper::GetIPAddrInfo(const char* ipaddress, uint16_t port) {
  addrinfo hints;
  addrinfo* results = NULL;
  sockaddr_in* addr_info_tmp = (sockaddr_in*)(m_addr_info);

  memset(&hints, 0, sizeof(addrinfo));
  hints.ai_family = PF_UNSPEC;
  hints.ai_socktype = SOCK_DGRAM;
  hints.ai_protocol = IPPROTO_UDP;

  int gaiResult = getaddrinfo(ipaddress, NULL, &hints, &results);
  if (gaiResult != 0 || !results || results->ai_family != AF_INET) {
    return -1;
  }

  memcpy((void*)addr_info_tmp, (void*)((sockaddr_in*)results->ai_addr),
         sizeof(sockaddr_in));
  addr_info_tmp->sin_port = htons(port);
  freeaddrinfo(results);

  return 0;
}

int32_t CSocketWrapper::Connect(const char* ipaddress, uint16_t port) {
  hostent* pHostent = gethostbyname(ipaddress);
  if (!pHostent) {
    log_error("gethostbyname failed(%s,err=%d)", ipaddress, GetSockError());
    return -1;
  }

  int32_t flags = SetNBIO();

  // get connect address data
  sockaddr_in clientAddr;
  clientAddr.sin_family = AF_INET;
  clientAddr.sin_port = htons(port);
  clientAddr.sin_addr = *((struct in_addr*)pHostent->h_addr);

  // record target information
  m_uTargetIP = clientAddr.sin_addr.s_addr;
  m_uTargetPort = port;

  // connect
  int32_t ret = ::connect(m_socket, (sockaddr*)&clientAddr, sizeof(clientAddr));
  if (ret == 0) {
    SetBIO(flags);
    return 0;
  }
#ifdef _MSC_VER
  else if (ret < 0 && GetSockError() == WSAEWOULDBLOCK)
#else
  else if (ret < 0 && errno == EINPROGRESS)
#endif
  {
    int32_t time_count = 20;
    while (--time_count >= 0 && m_socket != INVALID_SOCKET) {
      fd_set fd;
      FD_ZERO(&fd);
      FD_SET(m_socket, &fd);
      struct timeval tv;
      tv.tv_sec = 0;
      tv.tv_usec = 500000;  // 500ms

      int32_t ret = select(m_socket + 1, NULL, &fd, NULL, &tv);
      if (ret == SOCKET_ERROR) {
        if (IsClosed()) {
          return 0;
        }
        log_error("select falied, getlasterror(%d)", GetSockError());

        return -1;
      } else if (ret == 0) {
        continue;
      } else {
        if (m_socket == INVALID_SOCKET) {
          return 0;
        }
        if (FD_ISSET(m_socket, &fd)) {
          int32_t error = 0;
          socklen_t len = sizeof(error);
          int32_t ret =
              getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len);
          // 如果发生错误，Solaris实现的getsockopt返回-1,把pending
          // error设置给errno. Berkeley实现的getsockopt返回0, pending
          // error返回给error. 我们需要处理这两种情况
          if (ret < 0 || error) {
            log_error("FD_ISSET falied, ret(%d),getlasterror(%d)", ret,
                      GetSockError());

            return -1;
          }
          SetBIO(flags);
          return 0;
        } else {
          log_error("FD_ISSET falied,getlasterror(%d)", ret, GetSockError());

          return -1;
        }
      }
    }
    if (IsClosed()) {
      return 0;
    }
    if (time_count < 0) {
      log_error("select falied(timeout count 10), getlasterror(%d)",
                GetSockError());

      return -1;
    }
  } else {
    log_error("connect falied, getlasterror(%d)", GetSockError());
    return -1;
  }

  return -1;
}

int32_t CSocketWrapper::SetNBIO() {
#ifdef _MSC_VER
  u_long tmp = 1;
  if (ioctlsocket(m_socket, FIONBIO, &tmp) < 0) {
    log_error("ioctlsocket falied, mode(1),getlasterror(%d)", GetSockError());
  }
  return 0;
#else
  int32_t flags = fcntl(m_socket, F_GETFL, 0);
  flags = flags | O_NONBLOCK;
  fcntl(m_socket, F_SETFL, flags | O_NONBLOCK);

  return flags;
#endif
}

int32_t CSocketWrapper::SetBIO(int32_t mode) {
#ifdef _MSC_VER
  u_long tmp = mode;
  if (ioctlsocket(m_socket, FIONBIO, &tmp) < 0) {
    log_error("ioctlsocket falied, getlasterror(%d)", GetSockError());
  }
#else
  fcntl(m_socket, F_SETFL, mode);
#endif
  return 0;
}

#if _WIN32
uint32_t CSocketWrapper::GetSocket()
#else
int32_t CSocketWrapper::GetSocket()
#endif
{
  return m_socket;
}

int32_t CSocketWrapper::SendTo(const char* buff, uint32_t len) {
  if (IsClosed()) {
    return -1;
  }
  int32_t numBytes =
      sendto(m_socket, buff, len, 0, (const sockaddr*)(m_addr_info),
             sizeof(sockaddr_in));

  return numBytes;
}

int32_t CSocketWrapper::Send(const char* buff, uint32_t len) {
  if (IsClosed()) {
    return -1;
  }
  int32_t ret = send(m_socket, buff, len, 0);

  return ret;
}

int32_t CSocketWrapper::Select() {
  if (IsClosed()) {
    return -1;
  }

  while (!IsClosed()) {
    fd_set fdRead;
    FD_ZERO(&fdRead);
    FD_SET(m_socket, &fdRead);

    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 200000;  // 200ms

    int32_t ret = select(m_socket + 1, &fdRead, NULL, NULL, &tv);
    if (ret > 0) {
      return ret;
    } else if (ret < 0) {
      break;
    }
  }

  return -1;
}

int32_t CSocketWrapper::Recv(char* buff, uint32_t len) {
  if (IsClosed()) {
    return -1;
  }

  int32_t ret = recv(m_socket, buff, len, 0);

#ifdef _MSC_VER
  // The connection has been gracefully closed
  if (0 == ret) {
    log_error("recv error: [connection closed]!");
    return -2;
  }

  // Success, and return recv size
  if (SOCKET_ERROR != ret) {
    return ret;
  }

  // Operation now in progress
  uint32_t err_code = GetSockError();
  if (WSAEINPROGRESS == err_code) {
    log_warning("recv error: [WSAEINPROGRESS]");
    return 0;
  }

  // Message too long
  if (WSAEMSGSIZE == err_code) {
    log_warning("recv error: [WSAENOBUFS]");
    return 0;
  }

  // No buffer space available
  if (WSAENOBUFS == err_code) {
    log_warning("%recv error: [WSAENOBUFS]");
    return -3;
  }

  log_error("recv error: %d", err_code);
  return -1;
#else
  return ret;
#endif
}

int32_t CSocketWrapper::Recvfrom(char* buff, uint32_t len, void* props) {
  if (IsClosed()) {
    return -1;
  }
  socklen_t propLen = sizeof(sockaddr_in);
  int32_t numBytes =
      recvfrom(m_socket, buff, len, 0, (sockaddr*)props, &propLen);

  return numBytes;
}

uint32_t CSocketWrapper::GetTargetIP() { return m_uTargetIP; }

uint16_t CSocketWrapper::GetTargetPort() { return m_uTargetPort; }

bool CSocketWrapper::CompareAddrInfo(void* addr_info) {
  sockaddr_in* dst_addr_info = (sockaddr_in*)addr_info;
  sockaddr_in* src_addr_info = (sockaddr_in*)m_addr_info;
  if (memcmp(&dst_addr_info->sin_addr, &src_addr_info->sin_addr,
             sizeof(in_addr)) != 0 ||
      dst_addr_info->sin_port != src_addr_info->sin_port) {
    return false;
  }

  return true;
}

bool CSocketWrapper::IsInvalid() { return GetSocket() == INVALID_SOCKET; }

bool CSocketWrapper::IsClosed() { return m_closed; }
}  // namespace qb
